
// stroke codes:
// 横 HORIZ
// 竖 VERTI
// 撇 LEFAL
// 捺 RIFAL

function getEnd (side, move, end, ctrl, frag) {
  const {[side]:endPart, body: bodyPart} = polysSegSect([frag.poly], [end, ctrl]);

  let movedEnd;

  if (['B', 'F'].includes(move)) {
    
    const part = {B:endPart, F: bodyPart}[move];
    const getMethod = {B: 'last', F: side}[move];

    [{point: movedEnd}={}] = part[getMethod]() ?? [];

    if (!movedEnd) {
      movedEnd = end;
    } else {
    
      const dir = {B: 'copy', F: 'neg'}[move];
      const inc = end.diff(movedEnd)[dir]().mult(1E-6)
      movedEnd.plus_(inc);
      movedEnd.unextended = true;
    }
    
  } else {
    movedEnd = end;
  }

  return movedEnd;
}

function findSuccByPath(orig, path, delim = '/') {
  let curr = orig;
  if (path.length > 0) for (let seg of path.split(delim)) {
    if (seg === '..') {
      curr = curr.prev;
    } else {
      curr = curr.succ[seg];
    }
  }
  return curr;
}

function findPosByPath(orig, spec) {
  const [specPath, specPos] = spec.split(':');
  const frag = findSuccByPath(orig, specPath);

  return {
    frag, 
    pos: frag?.poss?.[specPos]
  };
}

const curvatures = {
  HORIZ(head, tail){

    const direction = tail.diff(head).norm().spin(90);

    const headCtrl = head.lerp(tail, 1/3).plus(direction.mult(EPS));
    const tailCtrl = head.lerp(tail, 2/3).plus(direction.mult(EPS));
  
    return [headCtrl, tailCtrl];
  },

  VERTI(head, tail){
    
    const headCtrl = head.lerp(tail, 1/3);
    const tailCtrl = head.lerp(tail, 2/3);
  
    return [headCtrl, tailCtrl];
  },

  LEFAL(head, tail){
    
    const direction = tail.diff(head).norm().spin(90);
    const angle = direction.cross({x:0, y:-1});

    const curvature = -Math.min(0.1, tail.dist(head)*0.05);
    console.log(tail.dist(head), 'CURVA');

    const tailCtrl = head.lerp(tail, 4/6).plus(direction.mult(curvature));
    tailCtrl.y = 0.1 * tailCtrl.y + 0.9 * tail.y;

    const headCtrl = head.lerp(tailCtrl, 1/3);
    const headDist = headCtrl.dist(head);
    console.log(headDist, 'DIST');
    headCtrl.x = 0.4 * headCtrl.x + 0.6 * head.x;
    headCtrl.y = (headDist > 0.3 ? headDist*1.2 : 0.1) + head.y;

    return [headCtrl, tailCtrl];
  },

  DOTTO(head, tail) {

    tail.plus_(head.diff(tail).mult(0.1));

    const direction = tail.diff(head).norm().spin(90);

    const curvature = -0.1;
    const headCtrl = head.lerp(tail, 1/2).plus(direction.mult(curvature));
    const tailCtrl = head.lerp(tail, 3/4).plus(direction.mult(curvature));
  
    return [headCtrl, tailCtrl];
  }
}

defaultCurvature = (head, tail) => {
    
  const headCtrl = head.lerp(tail, 1/3);
  const tailCtrl = head.lerp(tail, 2/3);

  return [headCtrl, tailCtrl];
}

function createStroke(orig, {head, tail, curvatureType}, offset) {

  console.log(offset);

  const [headPath, headMove] = head.split('=>');
  const [tailPath, tailMove] = tail.split('=>');

  const {frag: headFrag, pos: headEnd} = findPosByPath(orig, headPath);
  const {frag: tailFrag, pos: tailEnd} = findPosByPath(orig, tailPath);

  const [headCtrl, tailCtrl] = (curvatures[curvatureType] ?? defaultCurvature)(headEnd, tailEnd);

  const headMovedEnd = getEnd('head', headMove, headEnd, headCtrl, headFrag);
  const tailMovedEnd = getEnd('tail', tailMove, tailEnd, tailCtrl, tailFrag);

  return [headMovedEnd, headCtrl, tailCtrl, tailMovedEnd];
}

function posCentroids (poly, sects, eccen=0) {

  const cents = sects.reduce((polys, sect) => {
    return polySect(polys, sect, {applyEnds: true})
  }, [poly.slice()])
  .map(e => centroid(e))

  const polyCent = centroid(poly);

  return cents.map(e => polyCent.lerp(e, 1 + eccen))
}

function posAllCentroids (poly, eccen) {
  
  const C = cent = centroid(poly);

  const [U, D] = posCentroids(poly, [line({cent})], eccen);
  const [L, R] = posCentroids(poly, [line({angle: 90, cent})], eccen);
  const [UL, UR, DL, DR] = posCentroids(poly, [line({cent}), line({angle: 90, cent})], eccen);

  return {U, D, L, R, UL, UR, DL, DR, C}
} 

class Frag {

  constructor(vecs, prev) {
    this.poly = vecs.toClosure();
    
    this.sectSpec = [];

    this.sect = [];
    this.succ = [];
    this.prev = prev;

    this.poss = posAllCentroids(this.poly, 0.45);
  }

  trav(op, order='PRE') {
    (order === 'PRE') && op(this);

    for (let i = 0; i < this.succ.length; i++) {
      this.succ[i].trav(op, order);
    }
    
    (order === 'POST') && op(this);
  }

  applySect(vecs, affected) {
    vecs.affected = affected;
    
    this.sect.push(vecs);
    
    let polys = [this.poly.slice()];
    for (let i = 0; i < this.sect.length; i++) {
      const sectSeg = this.sect[i];
      polys = polySect(polys, sectSeg.slice(), {applyEnds:true, affected: sectSeg.affected});
    }

    this.succ = polys.map(p => new Frag(p, this));
  }

  addStrokeSpec(strokeSpec, affected) {
    this.sectSpec.push({
      strokeSpec,
      affected
    })
  }

  runStrokeSpec(overallOffset=new Vec()) {

    this.succ = [];
    this.sect = [];

    const sectPoints = new Set();

    for (let {strokeSpec, affected, offset} of this.sectSpec) {

      const strokes = [];

      for (let spec of strokeSpec) {

        let stroke = createStroke(this, spec, offset);

        strokes.push(...stroke);
      }
      // console.log(strokes);
      this.applySect(strokes, affected);
    }

    this.trav(frag => {
      for (let polyPoint of frag.poly) if (polyPoint.sect?.type === 'sect') {
        sectPoints.add(polyPoint);
      }
    })

    for (let point of sectPoints) {
      point.plus_(overallOffset);
    }
  }

  draw(ctx, {index, lineWidth=1}={}) {
    ctx.drawPoly(this.poly);

    if (index != undefined) {

      ctx.text(index, this.poss.C, {fontSize:40, fillStyle:'rgba(0, 0, 0, 0.1)'});

      for (let [k, v] of Object.entries(this.poss)) {
        if (k === 'C') {
          ctx.point(v, {fillStyle: 'black',});
        } else {
          ctx.text(k, v);
        }
      }
  
    }

    for (let sect of this.sect) {
      ctx.drawVecs(sect, {lineWidth, strokeStyle: '#000', drawBezier: true, drawControl:true});
    }

    for (let p of this.poly) if (p.sect){
      ctx.point(p, {fillStyle: 'rgba(128, 128, 0, 0.2)', radius: 3});
    }

    for (let [index, succ] of this.succ.entries()) {
      succ.draw(ctx, {index, lineWidth});
    }
  }
}